package com.doublechaintech.retailscm;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.MissingResourceException;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.doublechaintech.retailscm.services.IamService;

import com.doublechaintech.retailscm.secuser.SecUser;
import com.doublechaintech.retailscm.secuser.SecUserCustomManagerImpl;
import com.doublechaintech.retailscm.userapp.UserApp;
import com.doublechaintech.retailscm.userapp.UserAppTokens;
import com.terapico.utils.CollectionUtils;

public class RetailscmUserContextImpl extends UserContextImpl implements RetailscmUserContext {
  // implements the domain specific user model

  // 默认支持中文和英文
  protected static Map<String, String> chineseMap;

  protected static Map<String, String> englishMap;

  protected double longitude;
  protected double latitude;
  protected SecUser secUser;
  protected UserApp userApp;

  public SecUser getSecUser() {
    return secUser;
  }

  public void setSecUser(SecUser secUser) {
    this.secUser = secUser;
  }

  public UserApp getUserApp() {
    return userApp;
  }

  public void setUserApp(UserApp userApp) {
    this.userApp = userApp;
  }

  static final String RESOURCE_PATH = "com.doublechaintech.retailscm.RetailscmResources";
  static final String CUSTOM_RESOURCE_PATH =
      "com.doublechaintech.retailscm.RetailscmCustomResources";

  public Map<String, String> ensureLocaleMaps(Locale locale) {

    String[] resources = {RESOURCE_PATH, CUSTOM_RESOURCE_PATH};
    return ensureResourceAddResourceMaps(resources, locale);
  }

  protected Map<String, String> ensureResourceAddResourceMaps(String[] paths, Locale locale) {
    Map<String, String> localeMap = new HashMap<String, String>();
    for (String path : paths) {
      try {
        ResourceBundle resourceBundle = ResourceBundle.getBundle(path, locale);
        addResourceToMap(localeMap, resourceBundle);
      } catch (MissingResourceException e) {
        // the resource can be missed and it is fine
      }
    }
    return localeMap;
  }

  protected void addResourceToMap(Map<String, String> localeMap, ResourceBundle resourceBundle) {
    Enumeration<String> bundleKeys = resourceBundle.getKeys();

    while (bundleKeys.hasMoreElements()) {
      String key = (String) bundleKeys.nextElement();
      String value = resourceBundle.getString(key);
      // System.out.println("key = " + key + ", " + "value = " + value);
      localeMap.put(key, value);
    }
  }

  public void init() {
    if (chineseMap == null) {
      chineseMap = ensureLocaleMaps(Locale.SIMPLIFIED_CHINESE);
    }
    if (englishMap == null) {
      englishMap = ensureLocaleMaps(Locale.US);
    }
  }

  public Map<String, String> getLocaleMap() {
    init();

    return chineseMap;
  }

  protected Locale getLocale() {
    return Locale.US;
  }

  public String getLocaleKey(String subject) {
    return getLocaleMap().get(subject);
  }

  public double getLatitude() {
    return latitude;
  }

  public void setLatitude(double latitude) {
    this.latitude = latitude;
  }

  public double getLongitude() {
    return longitude;
  }

  public void setLongitude(double longitude) {
    this.longitude = longitude;
  }

  /*
   * public static SecUser getSecUser(RetailscmUserContext
   * userContext) { SecUserCustomManagerImpl secUserManager =
   * (SecUserCustomManagerImpl) userContext.getManagerGroup()
   * .getSecUserManager(); SecUser secUser =
   * secUserManager.getCachedSecUser(userContext); return secUser; }
   *
   * public static UserApp
   * getSingleUserAppByObjectName(RetailscmUserContext
   * userContext, String objectName) throws Exception { SecUserCustomManagerImpl
   * secUserManager = (SecUserCustomManagerImpl) userContext.getManagerGroup()
   * .getSecUserManager(); SecUser secUser =
   * secUserManager.getCachedSecUser(userContext); if (secUser == null) { return
   * null; } SmartList<UserApp> appList = getUserAppByName(userContext, secUser,
   * objectName); if (appList == null) { return null; } if (appList.size() == 1) {
   * return appList.get(0); } String appId =
   * secUserManager.getCurrentAppKey(userContext); for (UserApp app : appList) {
   * if (app.getId().equals(appId)) { return app; } } return null; }
   */
  public static SmartList<UserApp> getUserAppByName(
      RetailscmUserContext userContext, SecUser secUser, String objectName) {
    MultipleAccessKey key = new MultipleAccessKey();
    key.put(UserApp.SEC_USER_PROPERTY, secUser.getId());
    key.put(UserApp.APP_TYPE_PROPERTY, objectName);
    SmartList<UserApp> appList =
        userContext.getDAOGroup().getUserAppDAO().findUserAppWithKey(key, UserAppTokens.all());
    filterInvalidApps(appList);
    if (CollectionUtils.isEmpty(appList)) {
      return null;
    }
    return appList;
  }

  private static void filterInvalidApps(SmartList<UserApp> appList) {
    if (CollectionUtils.isEmpty(appList)) {
      return;
    }
    Iterator<UserApp> it = appList.iterator();
    while (it.hasNext()) {
      UserApp app = it.next();
      if (app.getSecUser() == null) {
        it.remove();
        continue;
      }
    }
  }

  public static UserApp getUserAppByBindedEntity(
      RetailscmUserContext userContext, BaseEntity userAppBindedObject) {
    MultipleAccessKey key = new MultipleAccessKey();
    key.put(UserApp.APP_TYPE_PROPERTY, userAppBindedObject.getInternalType());
    key.put(UserApp.APP_ID_PROPERTY, userAppBindedObject.getId());
    SmartList<UserApp> apps =
        userContext.getDAOGroup().getUserAppDAO().findUserAppWithKey(key, UserAppTokens.all());
    if (apps == null) {
      return null;
    }
    Iterator<UserApp> it = apps.iterator();
    while (it.hasNext()) {
      // 过滤掉无效的app
      UserApp app = it.next();
      if (app.getSecUser() == null) {
        it.remove();
        continue;
      }
    }
    if (apps.isEmpty()) {
      return null;
    }
    return apps.get(0);
  }

  public static UserApp getUserAppByBoundCtx(
      RetailscmUserContext userContext, BaseEntity userAppBindedObject) {
    MultipleAccessKey key = new MultipleAccessKey();
    key.put(UserApp.CTX_TYPE_PROPERTY, userAppBindedObject.getInternalType());
    key.put(UserApp.CTX_ID_PROPERTY, userAppBindedObject.getId());
    SmartList<UserApp> apps =
        userContext.getDAOGroup().getUserAppDAO().findUserAppWithKey(key, UserAppTokens.all());
    if (apps == null) {
      return null;
    }
    Iterator<UserApp> it = apps.iterator();
    while (it.hasNext()) {
      // 过滤掉无效的app
      UserApp app = it.next();
      if (app.getSecUser() == null) {
        it.remove();
        continue;
      }
    }
    if (apps.isEmpty()) {
      return null;
    }
    return apps.get(0);
  }

  private RetailscmObjectChecker checker;

  public void setChecker(RetailscmObjectChecker checker) {
    this.checker = checker;
  }

  @Override
  public RetailscmObjectChecker getChecker() {
    if (this.checker == null) {
      throw new IllegalStateException("每个实例必须配置Checker，请检查相关Spring的XML配置文件中 checker的配置");
    }
    checker.setUserContext(this);
    return checker;
  }

  protected static final String ACCESS_PARAMETERS_KEY = "$access_parameters";
  protected static final String ACCESS_METHOD_NAME_KEY = "$access_method_name";
  protected static final String ACCESS_BEAN_NAME_KEY = "$access_bean_name";

  public void saveAccessInfo(String beanName, String methodName, Object[] parameters) {
    putIntoContextLocalStorage(ACCESS_BEAN_NAME_KEY, beanName);
    putIntoContextLocalStorage(ACCESS_METHOD_NAME_KEY, methodName);
    putIntoContextLocalStorage(ACCESS_PARAMETERS_KEY, parameters);
    putToCache(getFootprintMarkKey(), true, 1 * 60 * 60);
  }

  protected String getFootprintMarkKey() {
    return tokenId() + ":$acces_page_without_foorprint";
  }

  public void addFootprint(FootprintProducer helper) throws Exception {
    String beanName = (String) this.getFromContextLocalStorage(ACCESS_BEAN_NAME_KEY);
    String methodName = (String) this.getFromContextLocalStorage(ACCESS_METHOD_NAME_KEY);
    Object[] parameters = (Object[]) this.getFromContextLocalStorage(ACCESS_PARAMETERS_KEY);
    if (beanName == null) {
      throw new Exception(
          "Please make sure you had invoke user-context saveAccessInfo() inside your onAccess().");
    }
    if (!beanName.equals(helper.getBeanName())) {
      throw new Exception(
          "This request was from " + beanName + ", not from " + helper.getBeanName() + ".");
    }
    if (methodName == null) {
      return; // 没保存过，就不用处理了。
    }
    this.removeFromCache(getFootprintMarkKey());
    // 核心问题是怎么处理堆栈。 是持续累加，还是‘短路’算法（clear_top)，或者‘提升’（brought—to-front）
    Footprint fp = new Footprint();
    fp.setBeanName(helper.getBeanName());
    fp.setMethodName(methodName);
    fp.setParameters(parameters);
    // 先从缓存中拿到历史记录
    List<Footprint> history = getFootprintListFromCache();
    if (history == null || history.isEmpty()) {
      // 历史是空的，直接追加
      history = new ArrayList<>();
      history.add(fp);
      this.log(
          "add footprint "
              + beanName
              + "."
              + methodName
              + "("
              + Arrays.asList(fp.getParameters())
              + ")");
      putFootprintIntoCache(history);
      return;
    }

    Footprint replacedFp = null;
    for (Footprint item : history) {
      if (helper.canReplaceFootPrint(fp, item)) {
        replacedFp = item;
        break;
      }
    }

    if (replacedFp == null) {
      // 没找到可替换的目标，追加到队列最后
      history.add(fp);
      this.log(
          "add new footprint "
              + beanName
              + "."
              + methodName
              + "("
              + Arrays.asList(fp.getParameters())
              + ")");
      putFootprintIntoCache(history);
      return;
    }

    // 找到可替换的目标以后，还要决定怎么做
    if (helper.clearTop()) {
      Iterator<Footprint> it = history.iterator();
      boolean found = false;
      while (it.hasNext()) {
        Footprint item = it.next();
        if (item == replacedFp) {
          found = true;
        }
        if (found) {
          it.remove();
        }
      }
    } else {
      int idx = history.indexOf(replacedFp);
      history.remove(idx);
    }
    history.add(fp);
    this.log(
        "replace footprint "
            + beanName
            + "."
            + methodName
            + "("
            + Arrays.asList(fp.getParameters())
            + ")");
    putFootprintIntoCache(history);
  }

  public int getFootprintDeepth() throws Exception {
    List<Footprint> history = getFootprintListFromCache();
    if (history == null || history.isEmpty()) {
      return 0;
    }
    return history.size();
  }

  protected void putFootprintIntoCache(List<Footprint> history) throws Exception {
    String historyJson = new ObjectMapper().writeValueAsString(history);
    this.putToCache(getFootprintKey(), historyJson, 1 * 60 * 60); // 1个小时
  }

  protected List<Footprint> getFootprintListFromCache() throws Exception {
    String historyJson = (String) this.getCachedObject(getFootprintKey(), String.class);
    if (historyJson == null || historyJson.isEmpty()) {
      return null;
    }
    return new ObjectMapper().readValue(historyJson, new TypeReference<List<Footprint>>() {});
  }

  protected String getFootprintKey() {
    return this.tokenId + ":footprint";
  }

  public Object getLastViewPage() throws Exception {
    List<Footprint> history = getFootprintListFromCache();
    if (history == null || history.isEmpty()) {
      return null;
    }

    Footprint fp = history.remove(history.size() - 1);
    putFootprintIntoCache(history);

    Object service = this.getBean(fp.getBeanName());
    fp.getParameters()[0] = this;
    Method[] methods = service.getClass().getMethods();
    for (Method m : methods) {
      if (m.getName().equals(fp.getMethodName())
          && m.getParameterTypes().length == fp.getParameters().length) {
        return m.invoke(service, fp.getParameters());
      }
    }
    return null;
  }

  public Object getPreviousViewPage() throws Exception {
    List<Footprint> history = getFootprintListFromCache();
    if (history == null || history.isEmpty()) {
      return null;
    }

    Footprint fp = history.remove(history.size() - 1);
    if (history.isEmpty()) {
      putFootprintIntoCache(history);
      return null;
    }
    fp = history.remove(history.size() - 1);
    putFootprintIntoCache(history);

    Object service = this.getBean(fp.getBeanName());
    fp.getParameters()[0] = this;
    Method[] methods = service.getClass().getMethods();
    for (Method m : methods) {
      if (m.getName().equals(fp.getMethodName())
          && m.getParameterTypes().length == fp.getParameters().length) {
        return m.invoke(service, fp.getParameters());
      }
    }
    return null;
  }

  public Object goback() throws Exception {
    Object mark = getCachedObject(getFootprintMarkKey(), Boolean.class);
    if (mark instanceof Boolean && ((Boolean) mark).booleanValue()) {
      // 没压栈
      return getLastViewPage();
    }
    return getPreviousViewPage();
  }

  @Override
  public <T extends BaseEntity> T reloadEntity(T stubEntity) throws Exception {
    return (T) getDAOGroup().loadBasicData(stubEntity.getInternalType(), stubEntity.getId());
  }

  public Object needLogin() {
    IamService iamService = Beans.getBean("iamService");
    Map<String, Object> loginInfo = iamService.getCachedLoginInfo(this);

    SecUser secUser = iamService.tryToLoadSecUser(this, loginInfo);
    UserApp userApp = iamService.tryToLoadUserApp(this, loginInfo);
    this.setSecUser(secUser);
    this.setUserApp(userApp);
    if (userApp != null) {
      userApp.setSecUser(secUser);
    }
    if (secUser == null) {
      iamService.onCheckAccessWhenAnonymousFound(this, loginInfo);
    }

    if (userApp == null) {
      LoginForm form = new LoginForm();
      form.addErrorMessage("没有选择app", null);
      return form;
    }
    return null;
  }
}
